#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "defs.h"
#include "externs.h"
#include "protos.h"
#include "pce.h"

/* locals */
static unsigned char buffer[16384];    /* buffer for .inc and .def directives */
static unsigned char header[512];    /* rom header */


/* ----
 * write_header()
 * ----
 * generate and write rom header
 */

void
pce_write_header(FILE *f, int banks)
{
    /* setup header */
    memset(header, 0, 512);
    header[0] = banks;

    /* write */
    fwrite(header, 512, 1, f);
}


/* ----
 * pack_8x8_tile()
 * ----
 * encode a 8x8 tile
 */

int
pce_pack_8x8_tile(unsigned char *buffer, void *data, int line_offset, int format)
{
    int i, j;
    int cnt;
    unsigned int   pixel, mask;
    unsigned char *ptr;
    unsigned int  *packed;

    /* pack the tile only in the last pass */
    if (pass != LAST_PASS)
        return (32);

    /* clear buffer */
    memset(buffer, 0, 32);

    /* encode the tile */
    switch (format) {
    case CHUNKY_TILE:
        /* 8-bit chunky format - from a pcx */
        cnt = 0;
        ptr = (unsigned char *)data;

        for (i = 0; i < 8; i++) {
            for (j = 0; j < 8; j++) {
                pixel = ptr[j ^ 0x07];
                mask  = 1 << j;
                buffer[cnt]    |= (pixel & 0x01) ? mask : 0;
                buffer[cnt+1]  |= (pixel & 0x02) ? mask : 0;
                buffer[cnt+16] |= (pixel & 0x04) ? mask : 0;
                buffer[cnt+17] |= (pixel & 0x08) ? mask : 0;
            }                
            ptr += line_offset;
            cnt += 2;
        }
        break;

    case PACKED_TILE:
        /* 4-bit packed format - from an array */
        cnt = 0;
        packed = (unsigned int *)data;
    
        for (i = 0; i < 8; i++) {
            pixel = packed[i];
    
            for (j = 0; j < 8; j++) {
                mask = 1 << j;
                buffer[cnt]    |= (pixel & 0x01) ? mask : 0;
                buffer[cnt+1]  |= (pixel & 0x02) ? mask : 0;
                buffer[cnt+16] |= (pixel & 0x04) ? mask : 0;
                buffer[cnt+17] |= (pixel & 0x08) ? mask : 0;
                pixel >>= 4;
            }
            cnt += 2;
        }
        break;

    default:
        /* other formats not supported */
        error("Internal error: unsupported format passed to 'pack_8x8_tile'!");
        break;
    }

    /* ok */
    return (32);
}


/* ----
 * pack_16x16_tile()
 * ----
 * encode a 16x16 tile
 */

int
pce_pack_16x16_tile(unsigned char *buffer, void *data, int line_offset, int format)
{
    int i, j;
    int cnt;
    unsigned int   pixel, mask;
    unsigned char *ptr;

    /* pack the tile only in the last pass */
    if (pass != LAST_PASS)
        return (128);

    /* clear buffer */
    memset(buffer, 0, 128);

    /* encode the tile */
    switch (format) {
    case CHUNKY_TILE:
        /* 8-bit chunky format - from a pcx */
        cnt = 0;
        ptr = (unsigned char *)data;

        for (i = 0; i < 16; i++) {
            for (j = 0; j < 8; j++) {
                pixel = ptr[j ^ 0x07];
                mask  = 1 << j;
                buffer[cnt]    |= (pixel & 0x01) ? mask : 0;
                buffer[cnt+1]  |= (pixel & 0x02) ? mask : 0;
                buffer[cnt+16] |= (pixel & 0x04) ? mask : 0;
                buffer[cnt+17] |= (pixel & 0x08) ? mask : 0;

                pixel = ptr[(j + 8) ^ 0x07];
                buffer[cnt+32] |= (pixel & 0x01) ? mask : 0;
                buffer[cnt+33] |= (pixel & 0x02) ? mask : 0;
                buffer[cnt+48] |= (pixel & 0x04) ? mask : 0;
                buffer[cnt+49] |= (pixel & 0x08) ? mask : 0;
            }                
            if (i == 7)
                cnt += 48;
            ptr += line_offset;
            cnt += 2;
        }
        break;

    default:
        /* other formats not supported */
        error("Internal error: unsupported format passed to 'pack_16x16_tile'!");
        break;
    }

    /* ok */
    return (128);
}


/* ----
 * pack_16x16_sprite()
 * ----
 * encode a 16x16 sprite
 */

int
pce_pack_16x16_sprite(unsigned char *buffer, void *data, int line_offset, int format)
{
    int i, j;
    int cnt;
    unsigned int   pixel, mask;
    unsigned char *ptr;
    unsigned int  *packed;

    /* pack the sprite only in the last pass */
    if (pass != LAST_PASS)
        return (128);

    /* clear buffer */
    memset(buffer, 0, 128);

    /* encode the sprite */
    switch (format) {
    case CHUNKY_TILE:
        /* 8-bit chunky format - from pcx */
        cnt = 0;
        ptr = (unsigned char *)data;

        for (i = 0; i < 16; i++) {
            /* right column */
            for (j = 0; j < 8; j++) {
                pixel = ptr[j ^ 0x0F];
                mask  = 1 << j;
                buffer[cnt]    |= (pixel & 0x01) ? mask : 0;
                buffer[cnt+32] |= (pixel & 0x02) ? mask : 0;
                buffer[cnt+64] |= (pixel & 0x04) ? mask : 0;
                buffer[cnt+96] |= (pixel & 0x08) ? mask : 0;
            }

            /* left column */
            for (j = 0; j < 8; j++) {
                pixel = ptr[j ^ 0x07];
                mask  = 1 << j;
                buffer[cnt+1]  |= (pixel & 0x01) ? mask : 0;
                buffer[cnt+33] |= (pixel & 0x02) ? mask : 0;
                buffer[cnt+65] |= (pixel & 0x04) ? mask : 0;
                buffer[cnt+97] |= (pixel & 0x08) ? mask : 0;
            }                
            ptr += line_offset;
            cnt += 2;
        }
        break;

    case PACKED_TILE:
        /* 4-bit packed format - from array */
        cnt = 0;
        packed = (unsigned int *)data;
    
        for (i = 0; i < 16; i++) {
            /* left column */
            pixel = packed[cnt];
    
            for (j = 0; j < 8; j++) {
                mask = 1 << j;
                buffer[cnt+1]  |= (pixel & 0x01) ? mask : 0;
                buffer[cnt+33] |= (pixel & 0x02) ? mask : 0;
                buffer[cnt+65] |= (pixel & 0x04) ? mask : 0;
                buffer[cnt+97] |= (pixel & 0x08) ? mask : 0;
                pixel >>= 4;
            }

            /* right column */
            pixel = packed[cnt + 1];
    
            for (j = 0; j < 8; j++) {
                mask = 1 << j;
                buffer[cnt]    |= (pixel & 0x01) ? mask : 0;
                buffer[cnt+32] |= (pixel & 0x02) ? mask : 0;
                buffer[cnt+64] |= (pixel & 0x04) ? mask : 0;
                buffer[cnt+96] |= (pixel & 0x08) ? mask : 0;
                pixel >>= 4;
            }
            cnt += 2;
        }
        break;

    default:
        /* other formats not supported */
        error("Internal error: unsupported format passed to 'pack_16x16_sprite'!");
        break;
    }

    /* ok */
    return (128);
}


/* ----
 * do_vram()
 * ----
 * .vram pseudo
 */

void
pce_vram(int *ip)
{
    /* define label */
    labldef(loccnt, 1);

    /* check if there's a label */
    if (lastlabl == NULL) {
        error("No label!");
        return;
    }

    /* get the VRAM address */
    if (!evaluate(ip, ';'))
        return;
    if (value >= 0x7F00) {
        error("Incorrect VRAM address!");
        return;
    }
    lastlabl->vram = value;

    /* output line */
    if (pass == LAST_PASS) {
        loadlc(value, 1);
        println();
    }
}


/* ----
 * do_pal()
 * ----
 * .pal pseudo
 */

void
pce_pal(int *ip)
{
    /* define label */
    labldef(loccnt, 1);

    /* check if there's a label */
    if (lastlabl == NULL) {
        error("No label!");
        return;
    }

    /* get the palette index */
    if (!evaluate(ip, ';'))
        return;
    if (value > 15) {
        error("Incorrect palette index!");
        return;
    }
    lastlabl->pal = value;

    /* output line */
    if (pass == LAST_PASS) {
        loadlc(value, 1);
        println();
    }
}


/* ----
 * do_defchr()
 * ----
 * .defchr pseudo
 */

void
pce_defchr(int *ip)
{
    unsigned int data[8];
    int size;
    int i;

    /* output infos */
    data_loccnt = loccnt;
    data_size   = 3;
    data_level  = 3;

    /* check if there's a label */
    if (lablptr) {
        /* define label */
        labldef(loccnt, 1);

        /* get the VRAM address */
        if (!evaluate(ip, ','))
            return;
        if (value >= 0x7F00) {
            error("Incorrect VRAM address!");
            return;
        }
        lablptr->vram = value;
    
        /* get the default palette */
        if (!evaluate(ip, ','))
            return;
        if (value > 0x0F) {
            error("Incorrect palette index!");
            return;
        }
        lablptr->pal = value;
    }

    /* get tile data */
    for (i = 0; i < 8; i++) {
        /* get value */
        if (!evaluate(ip, (i < 7) ? ',' : ';'))
            return;

        /* store value */
        data[i] = value;
    }

    /* encode tile */
    size = pce_pack_8x8_tile(buffer, data, 0, PACKED_TILE);

    /* store tile */
    putbuffer(buffer, size);

    /* output line */
    if (pass == LAST_PASS)
        println();
}


/* ----
 * do_defpal()
 * ----
 * .defpal pseudo
 */

void
pce_defpal(int *ip)
{
    unsigned int color;
    unsigned int r, g, b;
    unsigned char c;

    /* define label */
    labldef(loccnt, 1);

    /* output infos */
    data_loccnt = loccnt;
    data_size   = 2;
    data_level  = 3;

    /* get data */
    for (;;) {
        /* get color */
        if (!evaluate(ip, 0))
            return;

        /* store data on last pass */
        if (pass == LAST_PASS) {
            /* convert color */
            r = (value >> 8) & 0x7;
            g = (value >> 4) & 0x7;
            b = (value) & 0x7;
            color = (g << 6) + (r << 3) + (b);

            /* store color */
            putword(loccnt, color);
        }

        /* update location counter */
        loccnt += 2;

        /* check if there's another color */
        c = prlnbuf[(*ip)++];

        if (c != ',')
            break;
    }

    /* output line */
    if (pass == LAST_PASS)
        println();

    /* check errors */
    if (c != ';' && c != '\0')
        error("Syntax error!");
}


/* ----
 * do_defspr()
 * ----
 * .defspr pseudo
 */

void
pce_defspr(int *ip)
{
    unsigned int data[32];
    int size;
    int i;

    /* output infos */
    data_loccnt = loccnt;
    data_size   = 3;
    data_level  = 3;

    /* check if there's a label */
    if (lablptr) {
        /* define label */
        labldef(loccnt, 1);
    
        /* get the VRAM address */
        if (!evaluate(ip, ','))
            return;
        if (value >= 0x7F00) {
            error("Incorrect VRAM address!");
            return;
        }
        lablptr->vram = value;
    
        /* get the default palette */
        if (!evaluate(ip, ','))
            return;
        if (value > 0x0F) {
            error("Incorrect palette index!");
            return;
        }
        lablptr->pal = value;
    }

    /* get sprite data */
    for (i = 0; i < 32; i++) {
        /* get value */
        if (!evaluate(ip, (i < 31) ? ',' : ';'))
            return;

        /* store value */
        data[i] = value;
    }

    /* encode sprite */
    size = pce_pack_16x16_sprite(buffer, data, 0, PACKED_TILE);

    /* store sprite */
    putbuffer(buffer, size);

    /* output line */
    if (pass == LAST_PASS)
        println();
}


/* ----
 * do_incbat()
 * ----
 * build a BAT
 */

void
pce_incbat(int *ip)
{
    unsigned char *ptr, ref;
    unsigned int i, j, k, l, x, y, w, h;
    unsigned int base, index, flag;
    unsigned int temp;

    /* define label */
    labldef(loccnt, 1);

    /* output */
    if (pass == LAST_PASS)
        loadlc(loccnt, 0);

    /* get args */
    if (!pcx_get_args(ip))
        return;
    if (!pcx_parse_args(1, pcx_nb_args - 1, &x, &y, &w, &h, 8))
        return;

    /* build the BAT */
    if (pass == LAST_PASS) {
        index = 0;
        flag  = 0;
        base  = (pcx_arg[0] >> 4);

        for (i = 0; i < h; i++) {
            for (j = 0; j < w; j++) {
                ptr = pcx_buf + (x + (j * 8)) + ((y + (i * 8)) * pcx_w);
                ref = ptr[0] & 0xF0;

                /* check colors */
                for (k = 0; k < 8; k++) {
                    for (l = 0; l < 8; l++) {
                        if ((ptr[l] & 0xF0) != ref)
                            flag = 1;
                    }                
                    ptr += pcx_w;
                }
                temp = (base & 0xFFF) | ((ref & 0xF0) << 8);
                buffer[2*index]   = temp & 0xff;
                buffer[2*index+1] = temp >> 8;

                index++;
                base++;
            }
        }

        /* errors */
        if (flag)
            error("Invalid color index found!");
    }

    /* store data */
    putbuffer(buffer, 2 * w * h);

    /* output */
    if (pass == LAST_PASS)
        println();
}


/* ----
 * do_incpal()
 * ----
 * extract the palette of a PCX file
 */

void
pce_incpal(int *ip)
{
    unsigned int temp;
    int i, start, nb;
    int r, g, b;

    /* define label */
    labldef(loccnt, 1);

    /* output */
    if (pass == LAST_PASS)
        loadlc(loccnt, 0);

    /* get args */
    if (!pcx_get_args(ip))
        return;

    start = 0;
    nb = pcx_nb_colors;

    if (pcx_nb_args) {
        start = pcx_arg[0] << 4;

        if (pcx_nb_args == 1)
            nb = 16;
        else {
            nb = pcx_arg[1] << 4;
        }
    }

    /* check args */
    if (((start + nb) > 256) || (nb == 0)) {
        error("Palette index out of range!");
        return;
    }

    /* make data */
    if (pass == LAST_PASS) {
        for (i = 0; i < nb; i++) {
            r = pcx_pal[start + i][0];
            g = pcx_pal[start + i][1];
            b = pcx_pal[start + i][2];
            temp = ((r & 0xE0) >> 2) | ((g & 0xE0) << 1) | ((b & 0xE0) >> 5);
            buffer[2*i]   = temp & 0xff;
            buffer[2*i+1] = temp >> 8;
        }
    }

    /* store data */
    putbuffer(buffer, nb << 1);

    /* output */
    if (pass == LAST_PASS)
        println();
}


/* ----
 * do_incspr()
 * ----
 * PCX to sprites
 */

void
pce_incspr(int *ip)
{
    unsigned int i, j;
    unsigned int x, y, w, h;
    unsigned int sx, sy;
    unsigned int size;

    /* define label */
    labldef(loccnt, 1);

    /* output */
    if (pass == LAST_PASS)
        loadlc(loccnt, 0);

    /* get args */
    if (!pcx_get_args(ip))
        return;
    if (!pcx_parse_args(0, pcx_nb_args, &x, &y, &w, &h, 16))
        return;

    /* pack sprites */
    for (i = 0; i < h; i++) {
        for (j = 0; j < w; j++) {
            /* sprite coordinates */
            sx = x + (j << 4);
            sy = y + (i << 4);

            /* encode sprite */
            size = pcx_pack_16x16_sprite(buffer, sx, sy);

            /* store sprite */
            putbuffer(buffer, size);
        }
    }

    /* output */
    if (pass == LAST_PASS)
        println();
}


/* ----
 * do_inctile()
 * ----
 * PCX to 16x16 tiles
 */

void
pce_inctile(int *ip)
{
    unsigned int i, j;
    unsigned int x, y, w, h;
    unsigned int tx, ty;
    int nb_tile = 0;
    int size = 0;

    /* define label */
    labldef(loccnt, 1);

    /* output */
    if (pass == LAST_PASS)
        loadlc(loccnt, 0);

    /* get args */
    if (!pcx_get_args(ip))
        return;
    if (!pcx_parse_args(0, pcx_nb_args, &x, &y, &w, &h, 16))
        return;

    /* pack tiles */
    for (i = 0; i < h; i++) {
        for (j = 0; j < w; j++) {
            /* tile coordinates */
            tx = x + (j << 4);
            ty = y + (i << 4);

            /* get tile */
            size = pcx_pack_16x16_tile(buffer, tx, ty);

            /* store tile */
            putbuffer(buffer, size);
            nb_tile++;
        }
    }

    /* attach the number of loaded tiles to the label */
    if (lastlabl) {
        if (nb_tile) {
            lastlabl->nb = nb_tile;
            if (pass == LAST_PASS)
                lastlabl->size = size;
        }
    }

    /* output */
    if (pass == LAST_PASS)
        println();
}


/* ----
 * do_incmap()
 * ----
 * .incmap pseudo - convert a tiled PCX into a map
 */

void
pce_incmap(int *ip)
{
    unsigned int i, j;
    unsigned int x, y, w, h;
    unsigned int tx, ty;
    int tile, size;
    int err = 0;

    /* define label */
    labldef(loccnt, 1);

    /* output */
    if (pass == LAST_PASS)
        loadlc(loccnt, 0);

    /* get args */
    if (!pcx_get_args(ip))
        return;
    if (!pcx_parse_args(0, pcx_nb_args - 1, &x, &y, &w, &h, 16))
        return;

    /* pack map */
    for (i = 0; i < h; i++) {
        for (j = 0; j < w; j++) {
            /* tile coordinates */
            tx = x + (j << 4);
            ty = y + (i << 4);

            /* get tile */
            size = pcx_pack_16x16_tile(buffer, tx, ty);

            /* search tile */
            tile = pcx_search_tile(buffer, size);

            if (tile == -1) {
                /* didn't find the tile */
                tile = 0;
                err++;
            }

            /* store tile index */
            if (pass == LAST_PASS)
                putbyte(loccnt, tile & 0xFF);

            /* update location counter */
            loccnt++;
        }
    }

    /* error */
    if (err)
        error("One or more tiles didn't match!");

    /* output */
    if (pass == LAST_PASS)
        println();
}


/* ----
 * do_mml()
 * ----
 * .mml pseudo - music/sound macro language
 */

void
pce_mml(int *ip)
{
    int offset, bufsize, size;
    char mml[256];
    unsigned char c;

    /* define label */
    labldef(loccnt, 1);

    /* output */
    if (pass == LAST_PASS)
        loadlc(loccnt, 0);

    /* start */
    bufsize = bank_size;
    offset = mml_start(buffer);
    bufsize -= offset;

    /* extract and parse mml string(s) */
    for (;;) {
        /* get string */
        if (!getstring(ip, mml, sizeof(mml) - 1))
            return;

        /* parse string */
        size = mml_parse(buffer + offset, bufsize, mml);

        if (size == -1)
            return;

        /* adjust buffer size */
        offset  += size;
        bufsize -= size;

        /* next string */
        c = prlnbuf[(*ip)++];

        if ((c != ',') && (c != ';') && (c != '\0')) {
            error("Syntax error!");
            return;
        }
        if (c != ',')
            break;

        /* skip spaces */
        while (isspace(prlnbuf[*ip]))
            (*ip)++;

        /* continuation char */
        if (prlnbuf[*ip] == '\\') {
            (*ip)++;

            /* skip spaces */
            while (isspace(prlnbuf[*ip]))
                (*ip)++;

            /* check */
            if (prlnbuf[*ip] == ';' || prlnbuf[*ip] == '\0') {
                /* output line */
                if (pass == LAST_PASS) {
                    println();
                    clearln();
                }

                /* read a new line */
                if (readline() == -1)
                    return;
                
                /* rewind line pointer and continue */
                *ip = SFIELD;
            }
            else {
                error("Syntax error!");
                return;
            }
        }
    }            

    /* stop */
    offset += mml_stop(buffer + offset);

    /* store data */
    putbuffer(buffer, offset);

    /* output */
    if (pass == LAST_PASS)
        println();
}

